package utils

import org.apache.batik.transcoder.image.PNGTranscoder
import org.apache.batik.transcoder.{TranscoderInput, TranscoderOutput}
import org.apache.commons.io.{FileUtils, IOUtils}
import org.apache.commons.lang3.StringUtils
import tool.Tool

import java.io._
import java.util.zip.{GZIPInputStream, GZIPOutputStream}
import scala.io.Source
//import org.apache.commons.math3.stat.StatUtils
//import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation
//import org.saddle.io._
//import CsvImplicits._
//import javax.imageio.ImageIO
//import org.apache.pdfbox.pdmodel.PDDocument
//import org.apache.pdfbox.rendering.PDFRenderer
import scala.collection.JavaConverters._
import scala.collection.mutable
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}
//import scala.math.log10
import implicits.Implicits._

object Utils {

  val dbName = "p3_database"
  val orthoMclStr = "orthoMcl"
  val mummerStr = "mummer"
  val ardbStr = "ardb"
  val cazyStr = "cazy"

  val goPy = {
    val path = "C:\\Python\\python.exe"
    if (new File(path).exists()) path else "python"
  }

  val pyScript =
    """
      |<script>
      |Plotly.Plots.resize(document.getElementById($('#charts').children().eq(0).attr("id")));
      |window.addEventListener("resize", function (ev) {
      |				Plotly.Plots.resize(document.getElementById($('#charts').children().eq(0).attr("id")));
      |					})
      |</script>
      |
    """.stripMargin

  val phylotreeCss =
    """
      |<style>
      |.tree-selection-brush .extent {
      |    fill-opacity: .05;
      |    stroke: #fff;
      |    shape-rendering: crispEdges;
      |}
      |
      |.tree-scale-bar text {
      |  font: sans-serif;
      |}
      |
      |.tree-scale-bar line,
      |.tree-scale-bar path {
      |  fill: none;
      |  stroke: #000;
      |  shape-rendering: crispEdges;
      |}
      |
      |.node circle, .node ellipse, .node rect {
      |fill: steelblue;
      |stroke: black;
      |stroke-width: 0.5px;
      |}
      |
      |.internal-node circle, .internal-node ellipse, .internal-node rect{
      |fill: #CCC;
      |stroke: black;
      |stroke-width: 0.5px;
      |}
      |
      |.node {
      |font: 10px sans-serif;
      |}
      |
      |.node-selected {
      |fill: #f00 !important;
      |}
      |
      |.node-collapsed circle, .node-collapsed ellipse, .node-collapsed rect{
      |fill: black !important;
      |}
      |
      |.node-tagged {
      |fill: #00f;
      |}
      |
      |.branch {
      |fill: none;
      |stroke: #999;
      |stroke-width: 2px;
      |}
      |
      |.clade {
      |fill: #1f77b4;
      |stroke: #444;
      |stroke-width: 2px;
      |opacity: 0.5;
      |}
      |
      |.branch-selected {
      |stroke: #f00 !important;
      |stroke-width: 3px;
      |}
      |
      |.branch-tagged {
      |stroke: #00f;
      |stroke-dasharray: 10,5;
      |stroke-width: 2px;
      |}
      |
      |.branch-tracer {
      |stroke: #bbb;
      |stroke-dasharray: 3,4;
      |stroke-width: 1px;
      |}
      |
      |
      |.branch-multiple {
      |stroke-dasharray: 5, 5, 1, 5;
      |stroke-width: 3px;
      |}
      |
      |.branch:hover {
      |stroke-width: 10px;
      |}
      |
      |.internal-node circle:hover, .internal-node ellipse:hover, .internal-node rect:hover {
      |fill: black;
      |stroke: #CCC;
      |}
      |
      |.tree-widget {
      |}
      |</style>
    """.stripMargin

  def file2Lines(file: File) = {
    FileUtils.readLines(file).asScala
  }

  def lines2File(file: File, lines: mutable.Buffer[String], append: Boolean = false) = {
    FileUtils.writeLines(file, lines.asJava, append)
  }

  def createDirectoryWhenNoExist(file: File): Unit = {
    if (!file.exists && !file.isDirectory) file.mkdir
  }

  def getPrefix(file: File) = {
    val fileName = file.getName
    val index = fileName.lastIndexOf(".")
    fileName.substring(0, index)
  }

  def command2Wsl(command: String) = {
    if (Tool.isWindows) s"wsl ${command}" else command
  }

  def deleteDirectory(direcotry: File) = {
    try {
      FileUtils.deleteDirectory(direcotry)
    } catch {
      case _ =>
    }
  }

  def getTime(startTime: Long) = {
    val endTime = System.currentTimeMillis()
    (endTime - startTime) / 1000.0
  }

  def isDoubleP(value: String, p: Double => Boolean): Boolean = {
    try {
      val dbValue = value.toDouble
      p(dbValue)
    } catch {
      case _: Exception =>
        false
    }
  }

  def getGroupNum(content: String) = {
    content.split(";").size
  }

  def getMap(content: String) = {
    val map = mutable.LinkedHashMap[String, mutable.Buffer[String]]()
    content.split(";").foreach { x =>
      val columns = x.split(":")
      map += (columns(0) -> columns(1).split(",").toBuffer)
    }
    map
  }

  def getGroupNames(content: String) = {
    val map = getMap(content)
    map.keys.toBuffer

  }

  def replaceByRate(dataFile: File, rate: Double) = {
    val buffer = FileUtils.readLines(dataFile).asScala
    val array = buffer.map(_.split("\t"))
    val minValue = array.drop(1).flatMap(_.tail).filter(Utils.isDoubleP(_, _ > 0)).map(_.toDouble).min
    val rateMinValue = minValue * rate
    for (i <- 1 until array.length; j <- 1 until array(i).length) {
      if (Utils.isDoubleP(array(i)(j), _ == 0)) array(i)(j) = rateMinValue.toString
      if (StringUtils.isBlank(array(i)(j))) array(i)(j) = rateMinValue.toString
    }
    FileUtils.writeLines(dataFile, array.map(_.mkString("\t")).asJava)
  }

  def replaceByValue(dataFile: File, assignValue: String) = {
    val buffer = FileUtils.readLines(dataFile).asScala
    val array = buffer.map(_.split("\t"))
    for (i <- 1 until array.length; j <- 1 until array(i).length) {
      if (Utils.isDoubleP(array(i)(j), _ == 0)) array(i)(j) = assignValue
      if (StringUtils.isBlank(array(i)(j))) array(i)(j) = assignValue
    }
    FileUtils.writeLines(dataFile, array.map(_.mkString("\t")).asJava)
  }

  def relace0byNan(dataFile: File) = {
    val buffer = FileUtils.readLines(dataFile).asScala
    val array = buffer.map(_.split("\t"))
    for (i <- 1 until array.length; j <- 1 until array(i).length) {
      if (Utils.isDoubleP(array(i)(j), _ == 0)) array(i)(j) = "NA"
    }
    FileUtils.writeLines(dataFile, array.map(_.mkString("\t")).asJava)
  }

  def innerNormal(dataFile: File, colName: String, rowName: String) = {
    val buffer = FileUtils.readLines(dataFile).asScala
    val array = buffer.map(_.split("\t"))
    val colNum = array.take(1).flatten.indexOf(colName)
    val rowNum = array.map(_ (0)).indexOf(rowName)
    val div = array(rowNum)(colNum).toDouble
    val divArray = array(rowNum).drop(1).map(div / _.toDouble)
    for (i <- 1 until array.length; j <- 1 until array(i).length) {
      array(i)(j) = (array(i)(j).toDouble * divArray(j - 1)).toString
    }
    FileUtils.writeLines(dataFile, array.map(_.mkString("\t")).asJava)
  }

  def qcNormal(dataFile: File, colName: String) = {
    val buffer = FileUtils.readLines(dataFile).asScala
    val array = buffer.map(_.split("\t"))
    val colNum = array.head.indexOf(colName)
    for (i <- 1 until array.length) {
      val div = array(i)(colNum).toDouble
      for (j <- 1 until array(i).length) {
        array(i)(j) = (array(i)(j).toDouble / div).toString
      }
    }
    FileUtils.writeLines(dataFile, array.map(_.mkString("\t")).asJava)
  }

  def lfJoin(seq: Seq[String]) = {
    seq.mkString("\n")
  }

  def execFuture[T](f: Future[T]): T = {
    Await.result(f, Duration.Inf)
  }

  def peakAreaNormal(dataFile: File, coefficient: Double) = {
    val buffer = FileUtils.readLines(dataFile).asScala
    val array = buffer.map(_.split("\t"))
    val sumArray = new Array[Double](array(0).length)
    for (i <- 1 until array.length; j <- 1 until array(i).length) {
      sumArray(j) += array(i)(j).toDouble
    }
    for (i <- 1 until array.length; j <- 1 until array(i).length) {
      array(i)(j) = (coefficient * array(i)(j).toDouble / sumArray(j)).toString
    }
    FileUtils.writeLines(dataFile, array.map(_.mkString("\t")).asJava)
  }

  def dealGeneIds(geneId: String) = {
    geneId.split("\n").map(_.trim).distinct.toBuffer
  }

  def dealInputFile(file: File) = {
    val lines = FileUtils.readLines(file).asScala
    val buffer = lines.map(_.trim)
    FileUtils.writeLines(file, buffer.asJava)
  }

  def dealFileHeader(file: File) = {
    val lines = FileUtils.readLines(file).asScala
    val headers = lines(0).split("\t")
    println(headers(0))
    headers(0) = ""
    lines(0) = headers.mkString("\t")
    FileUtils.writeLines(file, lines.asJava)
  }


  //  def pdf2png(tmpDir: File, fileName: String) = {
  //    val pdfFile = new File(tmpDir, fileName)
  //    val outFileName = fileName.substring(0, fileName.lastIndexOf(".")) + ".png"
  //    val outFile = new File(tmpDir, outFileName)
  //    val document = PDDocument.load(pdfFile)
  //    val renderer = new PDFRenderer(document)
  //    ImageIO.write(renderer.renderImage(0, 3), "png", outFile)
  //    document.close()
  //  }
  //
  def getInfoByFile(file: File) = {
    val lines = FileUtils.readLines(file).asScala
    val columnNames = lines.head.split("\t").drop(1)
    val array = lines.drop(1).map { line =>
      val columns = line.split("\t")
      val map = mutable.Map[String, String]()
      map += ("geneId" -> columns(0))
      columnNames.zip(columns.drop(1)).foreach { case (columnName, data) =>
        map += (columnName -> data)
      }
      map
    }
    (columnNames, array)
  }

  def svg2png(file: File) = {
    val input = new TranscoderInput(file.toURI.toString)
    val outFile = new File(file.getParent, s"${Utils.getPrefix(file)}.png")
    val outStream = new FileOutputStream(outFile)
    val output = new TranscoderOutput(outStream)
    val t = new PNGTranscoder()
    t.transcode(input, output)
    outStream.flush()
    outStream.close()
  }

  def gZip(file: File, gzFile: File) = {
    val fin = new FileInputStream(file)
    val fout = new FileOutputStream(gzFile)
    val gzout = new GZIPOutputStream(fout)

    def loop: Unit = {
      val buf = new Array[Byte](1024)
      val num = fin.read(buf)
      num match {
        case x if x == -1 =>
        case _ =>
          gzout.write(buf, 0, num)
          loop
      }
    }

    loop
    gzout.close()
    fout.close()
    fin.close()
  }

  def unGzip(gzFile: File, outFile: File) = {
    val outStream = new FileOutputStream(outFile)
    val w = new PrintWriter(outStream)
    Utils.unGzip2Iter(gzFile) { lines =>
      lines.foreach { line =>
        w.write(s"${line}\n")
      }
    }
    w.close()
    outStream.close()
  }

  def unGzip2Iter(gzFile: File)(f: Iterator[String] => Unit) = {
    val inputStream = new GZIPInputStream(new BufferedInputStream(new FileInputStream(gzFile)))
    val iter = Source.fromInputStream(inputStream).getLines()
    f(iter)
    inputStream.close()
  }


}


